package org.budo.launcher.service.config;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;

import javax.servlet.DispatcherType;

import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.slf4j.Slf4j;
import org.budo.xml.XmlObject;
import org.slf4j.Logger;

import lombok.Getter;
import lombok.Setter;

/**
 * @author lmw
 * @see org.apache.tomcat.util.descriptor.web.WebXml
 */
@Getter
@Setter
public abstract class AbstractWebXmlConfigParser {
    private static final Logger log = Slf4j.getLogger();

    private XmlObject webAppConf;

    public abstract void addWelcomeFile(String welcomePage);

    public abstract void addErrorPage(String location, Integer errorCode);

    public abstract void addContextParam(String paramName, String paramValue);

    public abstract void addListener(String listenerClass);

    public abstract void addFilter(FilterConf filterConf);

    public abstract void addFilterUrlMapping(FilterMappingConf filterMappingConf);

    public abstract void addServlet(ServletConf servletConf);

    public void parseWebXml() {
        try {
            this.parseWelcomePageConf();
            this.parseErrorPageConf();
            this.parseContextParamConf();
            this.parseListenerConf();
            this.parseFilterConf();
            this.parseFilterUrlMappingConf();
            this.parseServletConf();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private void parseContextParamConf() {
        if (null == this.webAppConf) {
            return;
        }

        XmlObject[] contextParams = this.webAppConf.getChildNodeArrayByName("context-param");
        for (XmlObject contextParam : contextParams) {
            String paramName = contextParam.getChildNodeTextContentByName("param-name");
            String paramValue = contextParam.getChildNodeTextContentByName("param-value");

            if ("spring.profiles.active".equals(paramName) || "profiles.active".equals(paramName)) {
                if (null == paramValue || paramValue.startsWith("${")) {
                    log.error("#66 paramName=" + paramName + ", paramValue=" + paramValue);
                } else {
                    System.setProperty("spring.profiles.active", paramValue); // spring.profiles.active
                    System.setProperty("profiles.active", paramValue); // profiles.active
                }
            }

            log.info("#74 parseContextParamConf, paramName=" + paramName + ", paramValue=" + paramValue);
            this.addContextParam(paramName, paramValue);
        }
    }

    private void parseServletConf() throws Throwable {
        if (null == this.webAppConf) {
            return;
        }

        XmlObject[] servlets = this.webAppConf.getChildNodeArrayByName("servlet");
        for (XmlObject servlet : servlets) {
            String servletName = servlet.getChildNodeTextContentByName("servlet-name");
            String servletClass = servlet.getChildNodeTextContentByName("servlet-class");

            Map<String, String> initParamMap = MapUtil.stringMap();

            XmlObject[] initParams = servlet.getChildNodeArrayByName("init-param");
            if (null != initParams) {
                for (XmlObject initParam : initParams) {
                    String paramName = initParam.getChildNodeTextContentByName("param-name");
                    String paramValue = initParam.getChildNodeTextContentByName("param-value");
                    initParamMap.put(paramName, paramValue);
                }
            }

            ServletConf servletConf = new ServletConf(servletName, //
                    ReflectUtil.classForName(servletClass), //
                    initParamMap);

            XmlObject loadOnStartup = servlet.getChildNodeByName("load-on-startup");
            if (null != loadOnStartup) {
                Integer load = NumberUtil.toInteger(loadOnStartup.getTextContent());
                servletConf.setLoadOnStartup(load);
            }

            List<String> urlPatternList = new ArrayList<String>();
            XmlObject[] servletMappings = this.webAppConf.getChildNodeArrayByName("servlet-mapping");
            for (XmlObject servletMapping : servletMappings) {
                String mappingServletName = servletMapping.getChildNodeTextContentByName("servlet-name");
                if (mappingServletName.equals(servletName)) {
                    String urlPattern = servletMapping.getChildNodeTextContentByName("url-pattern");
                    urlPatternList.add(urlPattern);
                }
            }

            servletConf.setUrlPattern(urlPatternList.toArray(new String[0]));

            log.info("#90 addServlet, servletConf=" + servletConf);
            this.addServlet(servletConf);
        }
    }

    private void parseFilterUrlMappingConf() {
        XmlObject[] servletMappings = this.webAppConf.getChildNodeArrayByName("filter-mapping");
        for (XmlObject servletMapping : servletMappings) {
            FilterMappingConf filterMappingConf = new FilterMappingConf();
            String filterName = servletMapping.getChildNodeTextContentByName("filter-name");
            filterMappingConf.setFilterName(filterName);

            // url-pattern
            XmlObject[] urlPatterns = servletMapping.getChildNodeArrayByName("url-pattern");
            List<String> urlPatternList = new ArrayList<String>();
            for (int i = 0; i < urlPatterns.length; i++) {
                urlPatternList.add(urlPatterns[i].getTextContent());
            }
            filterMappingConf.setUrlPatternList(urlPatternList);

            // servlet-name
            XmlObject[] servletNames = servletMapping.getChildNodeArrayByName("servlet-name");
            List<String> servletNameList = new ArrayList<String>();
            for (int i = 0; i < servletNames.length; i++) {
                servletNameList.add(servletNames[i].getTextContent());
            }
            filterMappingConf.setServletNameList(servletNameList);

            // dispatcher
            XmlObject[] dispatchers = servletMapping.getChildNodeArrayByName("dispatcher");
            List<String> dispatcherList = new ArrayList<String>();
            for (int i = 0; i < dispatchers.length; i++) {
                dispatcherList.add(dispatchers[i].getTextContent());
            }
            filterMappingConf.setDispatcherTypeList(dispatcherList);

            this.addFilterUrlMapping(filterMappingConf);
        }
    }

    private void parseFilterConf() throws Throwable {
        if (null == this.webAppConf) {
            return;
        }

        XmlObject[] filters = this.webAppConf.getChildNodeArrayByName("filter");
        for (XmlObject filter : filters) {
            Map<String, String> initParamMap = MapUtil.stringMap();

            XmlObject[] initParams = filter.getChildNodeArrayByName("init-param");
            if (null != initParams) {
                for (XmlObject initParam : initParams) {
                    String paramName = initParam.getChildNodeTextContentByName("param-name");
                    String paramValue = initParam.getChildNodeTextContentByName("param-value");

                    initParamMap.put(paramName, paramValue);
                }
            }

            String filterName = filter.getChildNodeTextContentByName("filter-name");
            String filterClass = filter.getChildNodeTextContentByName("filter-class");

            FilterConf filterConf = new FilterConf(filterName, //
                    ReflectUtil.classForName(filterClass), //
                    initParamMap);

            this.addFilter(filterConf);

            log.info("#134 addFilter, filterConf=" + filterConf);
        }
    }

    private void parseErrorPageConf() throws Throwable {
        if (null == this.webAppConf) {
            return;
        }

        XmlObject[] errorPages = this.webAppConf.getChildNodeArrayByName("error-page");
        for (XmlObject errorPage : errorPages) {
            String location = errorPage.getChildNodeTextContentByName("location");
            int errorCode = NumberUtil.toInteger(errorPage.getChildNodeTextContentByName("error-code"));

            log.info("#129 addErrorPage, errorCode=" + errorCode + ", location=" + location);
            this.addErrorPage(location, errorCode);
        }
    }

    private void parseListenerConf() throws Throwable {
        if (null == this.webAppConf) {
            return;
        }

        XmlObject[] listeners = this.webAppConf.getChildNodeArrayByName("listener");
        for (XmlObject listener : listeners) {
            String listenerClass = listener.getChildNodeTextContentByName("listener-class");

            log.info("#139 addListener, listenerClass=" + listenerClass);
            this.addListener(listenerClass);
        }
    }

    private void parseWelcomePageConf() throws Throwable {
        if (null == this.webAppConf) {
            log.warn("#196 parseWelcomePageConf, webAppConf is null");
            return;
        }

        XmlObject welcomeFileList = this.webAppConf.getChildNodeByName("welcome-file-list");
        if (null == welcomeFileList) {
            return;
        }

        XmlObject[] welcomeFiles = welcomeFileList.getChildNodeArrayByName("welcome-file");
        if (null != welcomeFiles) {
            for (XmlObject _welcomeFile : welcomeFiles) {
                String welcomeFile = _welcomeFile.getTextContent();

                log.info("#162 addWelcomeFile, welcomeFile=" + welcomeFile);
                this.addWelcomeFile(welcomeFile);
            }
        }
    }

    /**
     * 默认返回 REQUEST
     */
    protected EnumSet<DispatcherType> getDispatcherTypeEnumSet(FilterMappingConf filterMappingConf) {
        List<String> dispatcherTypeList = filterMappingConf.getDispatcherTypeList();

        if (null == dispatcherTypeList || dispatcherTypeList.isEmpty()) {
            return EnumSet.of(DispatcherType.REQUEST);
        }

        String firstName = dispatcherTypeList.get(0);
        DispatcherType first = DispatcherType.valueOf(firstName);

        int len = dispatcherTypeList.size();
        if (len < 2) {
            return EnumSet.of(first);
        }

        DispatcherType[] rest = new DispatcherType[len - 1];
        for (int i = 1; i < len; i++) {
            String eachName = dispatcherTypeList.get(i);
            rest[i - 1] = DispatcherType.valueOf(eachName);
        }
        return EnumSet.of(first, rest);
    }
}